#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>
#include "main.h"

unsigned int line;
ast_token_ptr ast_result;

ast_token_ptr create_token(unsigned int type) {
	ast_token_ptr ret = malloc(sizeof(ast_token));
	memset(ret, 0, sizeof(ast_token));
	ret->line = line;
	ret->type = type;
	return(ret);
}

void yyerror(const char *str) {
	fprintf(stderr, "%s at %d near %s\n", str, yylval->line, yylval->stringValue);
	exit(-1);
}

void bytes_out(int count, const char *desc,...) {
	va_list ap;
	va_start(ap, desc);
	int i;
	for(i = 0; i < count; i++) {
		printf("0x%02x, ", va_arg(ap, int));
	}
	printf("%s\n", desc);

	va_end(ap);
	return;
}

char bytes_required(int w) {
	int i = 0;
	while(w >>= 8) i++;
	return(i+1);
}

int translate_arg_usage(const char *str) {
	if(!strcmp(str, "POINTER")) {
		return(0x01);
	} else if (!strcmp(str, "DIRECTION_X")) {
		return(0x30);
	} else if (!strcmp(str, "DIRECTION_Y")) {
		return(0x31);
	} else if (!strcmp(str, "JOYSTICK")) {
		return(0x04);
	} else if (!strcmp(str, "GAMEPAD")) {
		return(0x05);
	} else {
		fprintf(stderr, "Invalid arg: %s\n", str);
		exit(-1);
	}
}

int translate_arg_input(const char *str) {
	if(!strcmp(str, "Data,Var,Abs")) {
		return(0x02);
	} else {
		fprintf(stderr, "Invalid arg: %s\n", str);
		exit(-1);
	}
}

void walk(ast_token_ptr node) {
	char desc[64];
	ast_token_ptr arg;
	unsigned int first_byte = 0, second_byte = 0;
	if(node->next)
		walk(node->next);
	switch(node->type) {
		case EXPR:
			if(!strcmp(node->stringValue, "LOGICAL_MINIMUM")) {
				first_byte = 0x15;
				second_byte = node->child->i;
			} else if(!strcmp(node->stringValue, "LOGICAL_MAXIMUM")) {
				first_byte = 0x25;
				second_byte = node->child->i;
			} else if(!strcmp(node->stringValue, "PHYSICAL_MINIMUM")) {
				first_byte = 0x35;
				second_byte = node->child->i;
			} else if(!strcmp(node->stringValue, "PHYSICAL_MAXIMUM")) {
				first_byte = 0x45;
				second_byte = node->child->i;
			} else if(!strcmp(node->stringValue, "REPORT_SIZE")) {
				first_byte = 0x75;
				second_byte = node->child->i;
			} else if(!strcmp(node->stringValue, "REPORT_COUNT")) {
				first_byte = 0x95;
				second_byte = node->child->i;
			} else if(!strcmp(node->stringValue, "USAGE")) {
				first_byte = 0x09;
				if(node->child->data_type == SYMBOL)
					second_byte = translate_arg_usage(node->child->stringValue);
				else
					second_byte = node->child->i;
			} else if(!strcmp(node->stringValue, "INPUT")) {
				first_byte = 0x81;
				if(node->child->data_type == SYMBOL)
					second_byte = translate_arg_input(node->child->stringValue);
				else
					second_byte = node->child->i;
			} else {
				fprintf(stderr, "Invalid expr type: %s at %d\n", node->stringValue, node->line);
				exit(-1);
			}
			sprintf(desc, "// %s (%s)", node->stringValue, node->child->stringValue);
			bytes_out(2, desc, first_byte, second_byte);
			break;
		case BLOCK:
			arg = node->child;
			enum { NONE, UNKNOWN_ARGUMENT } error = NONE;
			// Printing header
			if(!strcmp(node->stringValue, "USAGE_PAGE")) {
				first_byte = 0x05;
				if(!strcmp(arg->stringValue, "GENERIC_DESKTOP"))
					second_byte = 0x01;
				else if(!strcmp(arg->stringValue, "BUTTON"))
					second_byte = 0x09;
				else
					error = UNKNOWN_ARGUMENT;
			} else if(!strcmp(node->stringValue, "COLLECTION")) {
				first_byte = 0xa1;
				if(!strcmp(arg->stringValue, "PHYSICAL"))
					second_byte = 0x00;
				else if(!strcmp(arg->stringValue, "APPLICATION"))
					second_byte = 0x01;
				else if(!strcmp(arg->stringValue, "LOGICAL"))
					second_byte = 0x02;
				else
					error = UNKNOWN_ARGUMENT;
			} else if(!strcmp(node->stringValue, "USAGE")) {
				first_byte = 0x09;
				if(node->child->data_type == SYMBOL)
					second_byte = translate_arg_usage(node->child->stringValue);
				else
					second_byte = node->child->i;
			} else {
				fprintf(stderr, "Invalid block type: %s at %d\n", node->stringValue, node->line);
				exit(-1);
			}
			if(error == UNKNOWN_ARGUMENT) {
				fprintf(stderr, "Unknown argument: %s at %d\n", arg->stringValue, arg->line);
				exit(-1);
			}
			// Dumping
			sprintf(desc, "// %s (%s)", node->stringValue, arg->stringValue);
			bytes_out(2, desc, first_byte, second_byte);
			// Entering recursion
			if(node->child->next) {
				assert(node->child->next->type == SPEC);
				walk(node->child->next);
			}
			// Printing footer
			if(!strcmp(node->stringValue, "COLLECTION")) {
				sprintf(desc, "// END COLLECTION");
				bytes_out(1, desc, 0xc0);
			}
			break;
		case SPEC:
			if(node->child)
				walk(node->child);
			break;
	}
}

int main() {
	yyparse();
	walk(ast_result);
	return 0;
}
